/* -------------------------------------------------------------------------
 *
 * ginlogic.cpp
 *	  routines for performing binary- and ternary-logic consistent checks.
 *
 * A GIN operator class can provide a boolean or ternary consistent
 * function, or both.  This file provides both boolean and ternary
 * interfaces to the rest of the GIN code, even if only one of them is
 * implemented by the opclass.
 *
 * Providing a boolean interface when the opclass implements only the
 * ternary function is straightforward - just call the ternary function
 * with the check-array as is, and map the GIN_TRUE, GIN_FALSE, GIN_MAYBE
 * return codes to TRUE, FALSE and TRUE+recheck, respectively.  Providing
 * a ternary interface when the opclass only implements a boolean function
 * is implemented by calling the boolean function many times, with all the
 * MAYBE arguments set to all combinations of TRUE and FALSE (up to a
 * certain number of MAYBE arguments).
 *
 * (A boolean function is enough to determine if an item matches, but a
 * GIN scan can apply various optimizations if it can determine that an
 * item matches or doesn't match, even if it doesn't know if some of the
 * keys are present or not.  That's what the ternary consistent function
 * is used for.)
 *
 *
 * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
 * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *    src/gausskernel/storage/access/gin/ginlogic.cpp
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "knl/knl_variable.h"

#include "access/gin_private.h"
#include "access/xloginsert.h"
#include "access/reloptions.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"

/*
 * Maximum number of MAYBE inputs that shimTriConsistentFn will try to
 * resolve by calling all combinations.
 */
#define MAX_MAYBE_ENTRIES 4

/*
 * Dummy consistent functions for an EVERYTHING key.  Just claim it matches.
 */
static bool trueConsistentFn(GinScanKey key)
{
    key->recheckCurItem = false;
    return true;
}
static GinTernaryValue trueTriConsistentFn(GinScanKey key)
{
    return GIN_TRUE;
}

/*
 * A helper function for calling a regular, binary logic, consistent function.
 */
static bool directBoolConsistentFn(GinScanKey key)
{
    /*
     * Initialize recheckCurItem in case the consistentFn doesn't know it
     * should set it.  The safe assumption in that case is to force recheck.
     */
    key->recheckCurItem = true;

    return DatumGetBool(FunctionCall8Coll(key->consistentFmgrInfo, key->collation, PointerGetDatum(key->entryRes),
                                          UInt16GetDatum(key->strategy), key->query, UInt32GetDatum(key->nuserentries),
                                          PointerGetDatum(key->extra_data), PointerGetDatum(&key->recheckCurItem),
                                          PointerGetDatum(key->queryValues), PointerGetDatum(key->queryCategories)));
}

/*
 * A helper function for calling a native ternary logic consistent function.
 */
static GinTernaryValue directTriConsistentFn(GinScanKey key)
{
    return DatumGetGinTernaryValue(
        FunctionCall7Coll(key->triConsistentFmgrInfo, key->collation, PointerGetDatum(key->entryRes),
                          UInt16GetDatum(key->strategy), key->query, UInt32GetDatum(key->nuserentries),
                          PointerGetDatum(key->extra_data), PointerGetDatum(key->queryValues),
                          PointerGetDatum(key->queryCategories)));
}

/*
 * This function implements a binary logic consistency check, using a ternary
 * logic consistent function provided by the opclass. GIN_MAYBE return value
 * is interpreted as true with recheck flag.
 */
static bool shimBoolConsistentFn(GinScanKey key)
{
    GinTernaryValue result;
    result = DatumGetGinTernaryValue(
        FunctionCall7Coll(key->triConsistentFmgrInfo, key->collation, PointerGetDatum(key->entryRes),
                          UInt16GetDatum(key->strategy), key->query, UInt32GetDatum(key->nuserentries),
                          PointerGetDatum(key->extra_data), PointerGetDatum(key->queryValues),
                          PointerGetDatum(key->queryCategories)));
    if (result == GIN_MAYBE) {
        key->recheckCurItem = true;
        return true;
    } else {
        key->recheckCurItem = false;
        return result;
    }
}

/*
 * This function implements a tri-state consistency check, using a boolean
 * consistent function provided by the opclass.
 *
 * Our strategy is to call consistentFn with MAYBE inputs replaced with every
 * combination of TRUE/FALSE. If consistentFn returns the same value for every
 * combination, that's the overall result. Otherwise, return MAYBE. Testing
 * every combination is O(n^2), so this is only feasible for a small number of
 * MAYBE inputs.
 *
 * NB: This function modifies the key->entryRes array!
 */
static GinTernaryValue shimTriConsistentFn(GinScanKey key)
{
    int nmaybe;
    int maybeEntries[MAX_MAYBE_ENTRIES];
    int i;
    bool boolResult = false;
    bool recheck = false;
    GinTernaryValue curResult;

    /*
     * Count how many MAYBE inputs there are, and store their indexes in
     * maybeEntries. If there are too many MAYBE inputs, it's not feasible to
     * test all combinations, so give up and return MAYBE.
     */
    nmaybe = 0;
    for (i = 0; i < (int)key->nentries; i++) {
        if (key->entryRes[i] == GIN_MAYBE) {
            if (nmaybe >= MAX_MAYBE_ENTRIES) {
                return GIN_MAYBE;
            }
            maybeEntries[nmaybe++] = i;
        }
    }

    /*
     * If none of the inputs were MAYBE, so we can just call consistent
     * function as is.
     */
    if (nmaybe == 0) {
        return (GinTernaryValue)(directBoolConsistentFn(key));
    }

    /* First call consistent function with all the maybe-inputs set FALSE */
    for (i = 0; i < nmaybe; i++) {
        key->entryRes[maybeEntries[i]] = GIN_FALSE;
    }
    curResult = (GinTernaryValue)(directBoolConsistentFn(key));

    for (;;) {
        /* Twiddle the entries for next combination. */
        for (i = 0; i < nmaybe; i++) {
            if (key->entryRes[maybeEntries[i]] == GIN_FALSE) {
                key->entryRes[maybeEntries[i]] = GIN_TRUE;
                break;
            } else {
                key->entryRes[maybeEntries[i]] = GIN_FALSE;
            }
        }
        if (i == nmaybe) {
            break;
        }

        boolResult = directBoolConsistentFn(key);
        recheck |= key->recheckCurItem;

        if (curResult != boolResult) {
            return GIN_MAYBE;
        }
    }

    /* TRUE with recheck is taken to mean MAYBE */
    if (curResult == GIN_TRUE && recheck) {
        curResult = GIN_MAYBE;
    }

    return curResult;
}

/*
 * Set up the implementation of the consistent functions for a scan key.
 */
void ginInitConsistentFunction(GinState *ginstate, GinScanKey key)
{
    if (key->searchMode == GIN_SEARCH_MODE_EVERYTHING) {
        key->boolConsistentFn = trueConsistentFn;
        key->triConsistentFn = trueTriConsistentFn;
    } else {
        key->consistentFmgrInfo = &ginstate->consistentFn[key->attnum - 1];
        key->triConsistentFmgrInfo = &ginstate->triConsistentFn[key->attnum - 1];
        key->collation = ginstate->supportCollation[key->attnum - 1];

        if (OidIsValid(ginstate->consistentFn[key->attnum - 1].fn_oid))
            key->boolConsistentFn = directBoolConsistentFn;
        else
            key->boolConsistentFn = shimBoolConsistentFn;

        if (OidIsValid(ginstate->triConsistentFn[key->attnum - 1].fn_oid))
            key->triConsistentFn = directTriConsistentFn;
        else
            key->triConsistentFn = shimTriConsistentFn;
    }
}
